/*
 * edu_fudan_lwang_CodecHelper.cpp
 *
 *  Created on: Dec 1, 2016
 *      Author: lwang
 */

#include <string>
#include <iostream>
#include <opencv2/opencv.hpp>

#include "../include/edu_fudan_lwang_CodecHelper.h"
#include "../include/codec_manager.h"

using namespace std;
using namespace cv;

static CodecManager codec_manager;

/**
 * ==============================================
 * type convert functions
 * ==============================================
 */

jstring charTojstring(JNIEnv* env, const char* pat) {
    jclass strClass = (env)->FindClass("Ljava/lang/String;");
    jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    jbyteArray bytes = (env)->NewByteArray(strlen(pat));
    (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat);
    jstring encoding = (env)->NewStringUTF("GB2312");
    return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding);
}

char* jstringToChar(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("GB2312");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

jbyteArray charTojbyteArray(JNIEnv *env, char* chs, int length) {
	jbyte *by = (jbyte*)chs;
	jbyteArray jarray = env->NewByteArray(length);
	env->SetByteArrayRegion(jarray, 0, length, by);
	return jarray;
}

char* jbyteArrayTochar(JNIEnv *env, jbyteArray jbyte_array, int& length) {
	jbyte* jbyte_ptr = (jbyte*)env->GetByteArrayElements(jbyte_array, 0);
	length = env->GetArrayLength(jbyte_array);
	return (char*)jbyte_ptr;
}

jintArray intArrayTojintArray(JNIEnv *env, int* ints, int length) {
	jint *jinteger = (jint*)ints;
	jintArray jarray = env->NewIntArray(length);
	env->SetIntArrayRegion(jarray, 0, length, jinteger);
	return jarray;
}

/**
 * ==============================================
 * JNI interface
 * ==============================================
 */

JNIEXPORT jint JNICALL Java_edu_fudan_lwang_CodecHelper_registerEncoder
  (JNIEnv *env, jobject obj, jstring encoder_id_jstr, jint codec_type, jint frame_width, jint frame_height) {

	string encoder_id_str = jstringToChar(env, encoder_id_jstr);
	return codec_manager.registerEnocder(encoder_id_str, codec_type, frame_width, frame_height);
}


JNIEXPORT jint JNICALL Java_edu_fudan_lwang_CodecHelper_registerDecoder
  (JNIEnv *env, jobject obj, jstring decoder_id_jstr, jint codec_type) {

	string encoder_id_str = jstringToChar(env, decoder_id_jstr);
	return codec_manager.registerDeocder(encoder_id_str, codec_type);
}


JNIEXPORT jbyteArray JNICALL Java_edu_fudan_lwang_CodecHelper_encodeFrame
  (JNIEnv *env, jobject obj, jstring encoder_id_jstr, jlong mat_addr) {

	string encoder_id_str = jstringToChar(env, encoder_id_jstr);
	BaseEncoder* encoder = codec_manager.getEncoderById(encoder_id_str);

	Mat* frame = (Mat*)(mat_addr);
//	Mat c_frame = frame->clone();
	unsigned char* buffer = new unsigned char[frame->rows * frame->cols * frame->channels()];
	int encoded_data_length = 0;

	encoder->encodeFrame(frame->data, buffer, encoded_data_length);
//		cout << "[Encoder " << encoder_id_str <<"] start encoding, Raw data size : "
//				<< frame->dataend - frame->datastart <<  endl;

	return charTojbyteArray(env, (char*)buffer, encoded_data_length);
}


JNIEXPORT void JNICALL Java_edu_fudan_lwang_CodecHelper_decodeFrame
  (JNIEnv *env, jobject obj, jstring decoder_id_jstr, jbyteArray encode_data_jarray,
		  jintArray results_jarray, jlong decode_yuv_mataddr) {

	string decoder_id_str = jstringToChar(env, decoder_id_jstr);
	BaseDecoder* decoder = codec_manager.getDecoderById(decoder_id_str);

	Mat *decode_yuv_mat = (Mat*)decode_yuv_mataddr;

	int encode_data_length = 0;
	unsigned char* encode_data = (unsigned char*)jbyteArrayTochar(env, encode_data_jarray, encode_data_length);

	int gotFrame = 0;
	int used_data_length = 0;
	decoder->decodeFrame(*decode_yuv_mat, gotFrame, encode_data, encode_data_length, used_data_length);

	int current_result_num = 2;
	int result[] = {gotFrame, used_data_length};

	if(results_jarray && env->GetArrayLength(results_jarray) >= current_result_num) {
		env->SetIntArrayRegion(results_jarray, 0, current_result_num, result);
	}
	else {
		results_jarray = intArrayTojintArray(env, result, current_result_num);
	}

}


JNIEXPORT void JNICALL Java_edu_fudan_lwang_CodecHelper_releaseEncoder
  (JNIEnv *env, jobject obj, jstring encoder_id_jstr) {

	string encoder_id_str = jstringToChar(env, encoder_id_jstr);
	codec_manager.releaseEncoder(encoder_id_str);
}


JNIEXPORT void JNICALL Java_edu_fudan_lwang_CodecHelper_releaseDecoder
  (JNIEnv *env, jobject obj, jstring decoder_id_jstr) {

	string decoder_id_str = jstringToChar(env, decoder_id_jstr);
	codec_manager.releaseDecoder(decoder_id_str);
}

